﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;

namespace StyleMVVM.Utilities
{
	public class WeakFunc<T>
	{
		private readonly Func<object, T> executeAction;
		private readonly WeakReference reference;

		public WeakFunc(Func<T> action)
		{
			if (action.Target != null)
			{
				reference = new WeakReference(action.Target);

				MethodInfo methInfo = action.GetMethodInfo();

				ParameterExpression param = Expression.Parameter(typeof(object), "target");

				UnaryExpression cast =
					Expression.ConvertChecked(param, action.Target.GetType());

				Expression call = Expression.Call(cast, methInfo);

				executeAction =
					Expression.Lambda<Func<object, T>>(call, param).Compile();
			}
			else
			{
				throw new ArgumentNullException("Action.Target can not be null");
			}
		}

		public bool IsAlive
		{
			get { return reference.IsAlive; }
		}

		public T Execute()
		{
			object target = reference.Target;

			if (executeAction != null && target != null)
			{
				return executeAction(target);
			}

			return default(T);
		}
	}

	public class WeakFunc<TIn, TOut>
	{
		private readonly Func<object, TIn, TOut> executeAction;
		private readonly WeakReference reference;

		public WeakFunc(Func<TIn, TOut> action)
		{
			if (action.Target != null)
			{
				reference = new WeakReference(action.Target);

				MethodInfo methInfo = action.GetMethodInfo();

				ParameterExpression targetParam = Expression.Parameter(typeof(object), "target");

				ParameterExpression tInParam = Expression.Parameter(typeof(TIn), "tIn");

				UnaryExpression cast =
					Expression.ConvertChecked(targetParam, action.Target.GetType());

				Expression call = Expression.Call(cast, methInfo, tInParam);

				executeAction =
					Expression.Lambda<Func<object, TIn, TOut>>(call, targetParam, tInParam).Compile();
			}
			else
			{
				throw new ArgumentNullException("Action.Target can not be null");
			}
		}

		public bool IsAlive
		{
			get { return reference.IsAlive; }
		}

		public TOut Execute(TIn paramIn)
		{
			object target = reference.Target;

			if (executeAction != null && target != null)
			{
				return executeAction(target, paramIn);
			}

			return default(TOut);
		}
	}
}